﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SystemUnitClassViewModel.cs" >
//   J. Prinz
// </copyright>
// <summary>
//   ViewModel for the Representation of a SystemUnitClass of a <see cref="SystemUnitClassType" />.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace AutomationMLMapper.ViewModels.CAEXObjectViewModels
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Media.Imaging;
    using System.Xml;
    using AMLEngineExtensions;
    using CAEX_ClassModel;
    using AutomationMLMapper.AutomationML.Extensions;
    using AutomationMLMapper.ViewModels.CAEXObjectCollectionViewModels;
    using AutomationMLMapper.ViewModels.PaneViewModels;
    using AutomationMLMapper.ViewModels.SystemViewModels;
    using AutomationMLMapper.MVVM_Framework;

    /// <summary>
    ///   ViewModel for the Representation of a SystemUnitClass of a <see cref="SystemUnitClassType" />.
    /// </summary>
    public class SystemUnitClassViewModel : CAEXObjectWithAttributesViewModel<SystemUnitClassType>
    {
        #region Fields

        /// <summary>
        ///  <see cref="IsExpanded"/>
        /// </summary>
        private bool isExpanded;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SystemUnitClassViewModel"/> class. 
        /// </summary>
        /// <param name="systemUnitClass">
        /// a CAEX - SystemUnitClassType-Element. 
        /// </param>
        public SystemUnitClassViewModel(SystemUnitClassType systemUnitClass)
            : base(systemUnitClass)
        {
            this.RolesCollection = new SupportedRoleClassCollectionViewModel(this) { IsExpanded = true };
            this.InstancesCollection = new InternalElementCollectionViewModel(this) { IsExpanded = true };
            this.PropertySetCollection = new PropertySetCollectionViewModel(this) { IsExpanded = false };
            this.ImportedInstancesCollection = new ImportInternalElementCollectionViewModel(this) { IsExpanded = true };

            this.PropertySetCollection.AllowCommand += this.PropertySetCollectionAllowCommand;
            this.IsExpanded = false;

            this.InstancesCollection.AllowCommand += this.InstancesCollectionAllowCommand;
            this.InstancesCollection.AfterExecuteCommand += this.InstancesCollectionAfterExecuteCommand;

            this.ImportedInstancesCollection.AfterExecuteCommand += this.ImportedInstancesCollectionAfterExecuteCommand;
            this.RolesCollection.AllowCommand += this.RolesCollectionAllowCommand;
            this.RolesCollection.BeforeExecuteCommand += this.RolesCollectionBeforeExecuteCommand;
            this.RolesCollection.AfterExecuteCommand += RolesCollection_AfterExecuteCommand;

            this.RolesCollection.ShowRoleAttributes = true;
            
        }

        void RolesCollection_AfterExecuteCommand(object sender, CollectionCommandEventArgs<RoleClassViewModel> commandArgs)
        {
            OnNotifyPropertyChanged("HasSupportedRoles");
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   DisplayName of the CAEX-Element in the UI used in Headers and Labels
        /// </summary>
        public static new string DisplayName
        {
            get
            {
                return "SystemUnitClass";
            }
        }

        /// <summary>
        ///   Collection of InternalElement Instances <see cref="ImportInternalElementCollectionViewModel" /> of the SystemUnitClass after Import
        /// </summary>
        public ImportInternalElementCollectionViewModel ImportedInstancesCollection { get; set; }

        /// <summary>
        ///   Collection of InternalElement Instances <see cref="InternalElementCollectionViewModel" /> of the SystemUnitClass
        ///   These InternalElements are AutomationML Objects which have a ClassReference to this SystemUnitClass
        /// </summary>
        public InternalElementCollectionViewModel InstancesCollection { get; set; }

        /// <summary>
        ///   State of Expansion of the SystemUnitClass
        /// </summary>
        public bool IsExpanded
        {
            get
            {
                return this.isExpanded;
            }

            set
            {
                this.isExpanded = value;
                this.OnNotifyPropertyChanged("IsExpanded");
            }
        }

        /// <summary>
        ///   Checks Validation State of the SystemUnitClass and all its Child-Elements
        /// </summary>
        public override bool IsValid
        {
            get
            {
                return base.IsValid
                           ? this.InstancesCollection.Elements.All(p => p.IsValid)
                             || this.ImportedInstancesCollection.Elements.All(p => p.IsValid)
                           : base.IsValid;
            }

            set
            {
                base.IsValid = value;
            }
        }

        /// <summary>
        ///   The Collection of PropertySets <see cref="PropertySetCollectionViewModel" /> of the SystemUnitClass
        /// </summary>
        public PropertySetCollectionViewModel PropertySetCollection { get; set; }

        /// <summary>
        ///   Collection of supported RoleClasses <see cref="SupportedRoleClassCollectionViewModel" /> of the SystemUnitClass
        /// </summary>
        public SupportedRoleClassCollectionViewModel RolesCollection { get; set; }

        /// <summary>
        ///   The SimulatedSystem which contains this class
        /// </summary>
        public SystemViewModel TheSimulatedSystem { get; set; }

        /// <summary>
        ///   True, if the <see cref="RolesCollection"/> contains any Element
        /// </summary>
        public bool HasSupportedRoles { get { return RolesCollection.Elements.Count > 0; } }


        /// <summary>
        ///  <see cref="ShowSupportedRoles"/>
        /// </summary>    
        private RelayCommand<object> showSupportedRoles;

        /// <summary>
        ///  The ShowSupportedRoles - Command
        /// </summary>
        public System.Windows.Input.ICommand ShowSupportedRoles
        {
            get
            {
                return this.showSupportedRoles
                ??
                (this.showSupportedRoles = new RelayCommand<object>(this.ShowSupportedRolesExecute, this.ShowSupportedRolesCanExecute));
            }
        }

        /// <summary>
        ///  The <see cref="ShowSupportedRoles"/> Execution Action.
        /// </summary>
        /// <param name="parameter">
        ///  TODO The parameter. 
        /// </param>        
        private void ShowSupportedRolesExecute(object parameter)
        { 
            SupportedRolesViewModel srviewModel = AutomationMLMapperViewModel.MainViewModel.AutomationMLTools.OfType<SupportedRolesViewModel>().SingleOrDefault() as SupportedRolesViewModel;
           
            if (srviewModel == null)
            {
                srviewModel = new SupportedRolesViewModel()
                {                    
                    CollectionOwner = this,
                    IsFloating = true,
                    RolesCollection = this.RolesCollection
                };

                srviewModel.RolesCollection.IsExpanded = true;
                AutomationMLMapperViewModel.MainViewModel.AddAutomationMLTool(srviewModel);
            }
            else
            {
                srviewModel.CollectionOwner = this;
                srviewModel.RolesCollection = this.RolesCollection;                
            }           
        }

        /// <summary>
        ///  Test, if the <see cref="ShowSupportedRoles"/> can execute.
        /// </summary>
        /// <param name="parameter">
        ///  TODO The parameter. 
        /// </param>  
        /// <returns>
        ///  true, if command can execute
        /// </returns> 
        private bool ShowSupportedRolesCanExecute(object parameter)
        {
            return true;
        }
        

        #endregion

        #region Public Indexers

        /// <summary>
        /// Validation Property
        /// </summary>
        /// <param name="propertyName">
        /// Name of Property.
        /// </param>
        /// <returns>
        /// The System.String. 
        /// </returns>
        public override string this[string propertyName]
        {
            get
            {
                if (!this.IsValid)
                {
                    // base class performs name validation test for empty string
                    var result = base[propertyName];
                    if (result != null)
                    {
                        return result;
                    }


                    switch (propertyName)
                    {
                        case "Name":
                            if (!this.TheSimulatedSystem.IsUnique(this))
                            {
                                return "Please enter a unique Classname";
                            }

                            this.IsValid = true;
                            break;
                    }
                }

                return null;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        ///  The AddElement Method adds a caexObject to a specific collection of the SystemUnitClass, depending on
        ///  the provided CaexObjectType
        /// </summary>
        /// <param name="caexObjectType">
        ///  ViewModel - Type of the added Element. Possible Types are <see cref="PropertySetViewModel"/>, 
        ///  <see cref="AttributeViewModel"/> or <see cref="InternalElementViewModel"/>.
        /// </param>
        /// <returns>
        /// The interface to the added CAEXObject.
        /// </returns>
        public override ICAEXObject AddElement(Type caexObjectType)
        {
            ICAEXObject result = null;
            if (caexObjectType == typeof(PropertySetViewModel))
            {
                var roleClass = this.TheSimulatedSystem.RolesCollection.View.CurrentItem as RoleClassViewModel;

                if (roleClass != null)
                {
                    var internalElement = this.CaexObject.New_InternalElement("new" + roleClass.Name);
                    var roleRequirements = internalElement.New_RoleRequirements();
                    roleRequirements.RefBaseRoleClassPath.Value = roleClass.CaexObject.CAEXPath();

                    foreach (var attribute in roleClass.CaexObject.GetOwnAndDerivedAttributes())
                    {
                        roleRequirements.Insert_Attribute((AttributeType)attribute.clone());
                    }

                    result = new PropertySetViewModel(internalElement) { Owner = this };
                }
            }
            else if (caexObjectType == typeof(AttributeViewModel))
            {
                result = new AttributeViewModel(this.CaexObject.New_Attribute(string.Empty));
            }
            else if (caexObjectType == typeof(InternalElementViewModel))
            {
                if (this.CaexObject is SystemUnitFamilyType)
                {
                    var instance = (this.CaexObject as SystemUnitFamilyType).CreateClassInstance();
                    if (instance != null)
                    {
                        var internalElement = instance.Node.CreateCAEXWrapper<InternalElementType>();
                        var roleRequirement = internalElement.New_RoleRequirements();

                        var ih = this.GetInstanceHierarchy();
                        if (ih != null)
                        {
                            ih.Insert_InternalElement(internalElement);
                        }

                        var roleClassViewModel = this.RolesCollection.View.CurrentItem as RoleClassViewModel;
                        if (roleClassViewModel != null)
                        {
                            roleRequirement.RefBaseRoleClassPath.Value = roleClassViewModel.CaexObject.CAEXPath();
                        }

                        result = new InternalElementViewModel(internalElement);
                    }
                }
            }
            else if (caexObjectType == typeof(RoleClassViewModel))
            {
                var roleClass = this.TheSimulatedSystem.RolesCollection.View.CurrentItem as RoleClassViewModel;

                if (roleClass != null)
                {
                    this.CaexObject.New_SupportedRoleClass(roleClass.CaexObject.CAEXPath());
                    result = roleClass;
                }
            }

            if (result != null)
            {
                base.AddElement(caexObjectType);
            }

            return result;
        }

        /// <summary>
        ///   load method of the SystemUnitClass. This Method will load all SupportedRoleClasses <seealso cref="RolesCollection" />,
        ///   Instances <seealso cref="InstancesCollection" /> and PropertySets  <seealso cref="PropertySetCollection" />
        /// </summary>
        public override void LoadCaexToRepresentationModel()
        {
            base.LoadCaexToRepresentationModel();

            foreach (SupportedRoleClassType src in this.CaexObject.SupportedRoleClass)
            {
                if (src.RefRoleClassPath.Exists())
                {
                    var roleClassView =
                        this.TheSimulatedSystem.RolesCollection.Elements.FirstOrDefault(
                            p => p.CaexObject.CAEXPath() == src.RefRoleClassPath.Value);
                    if (roleClassView != null)
                    {
                        this.RolesCollection.Elements.Add(roleClassView);
                    }
                }
            }

            var instances = this.GetAllSystemUnitClassInstances();
            foreach (var instance in from XmlNode instanceNode in instances select new InternalElementViewModel(instanceNode.CreateCAEXWrapper<InternalElementType>()))
            {
                instance.LoadCaexToRepresentationModel();
                this.InstancesCollection.Elements.Add(instance);
            }

            foreach (var propertySet in this.CaexObject.GetAllPropertySetElementsDeep().Select(internalElement => new PropertySetViewModel(internalElement) { Owner = this }))
            {
                propertySet.LoadCaexToRepresentationModel();
                this.PropertySetCollection.Elements.Add(propertySet);
            }
        }

        /// <summary>
        ///   ReValidation of the SystemUnitClass and all its ChildElements. Results are bound to UI-Elements
        /// </summary>
        public override void ReValidate()
        {
            this.InstancesCollection.Elements.ForEach(p => p.ReValidate());
            this.ImportedInstancesCollection.Elements.ForEach(p => p.ReValidate());
        }

        #endregion

        #region Methods

        /// <summary>
        /// Import the ImportedElement and change the ClassReference
        /// </summary>
        /// <param name="caexObject">
        /// the caexObject which should be imported 
        /// </param>
        /// <returns>
        /// The AutomationMLMapper.ViewModels.CAEXObjectViewModels.InternalElementViewModel. 
        /// </returns>
        internal InternalElementViewModel CreateInstanceFromImportedElement(CAEXObject caexObject)
        {
            // copy Element to the Document of this Class
            var ie = this.CopyInternalElementToDocument(caexObject);

            var internalElementModel = new ImportInternalElementViewModel(ie)
                {
                   LibraryCaexObject  = this.GetInstanceHierarchy(),
                   ContainerViewModel = this
                };
            internalElementModel.LoadCaexToRepresentationModel();
            this.ImportedInstancesCollection.Elements.Add(internalElementModel);

            return internalElementModel;
        }

        /// <summary>
        /// Import the ImportedElement and change the ClassReference and the RoleRequirement
        /// </summary>
        /// <param name="caexObject">
        /// the caexObject which should be imported 
        /// </param>
        /// <param name="supportedRoleClassType">
        /// the RoleClassType added as a RoleRequirement to the imported CaexObject 
        /// </param>
        internal void CreateInstanceFromImportedElement(
            CAEXObject caexObject, SupportedRoleClassType supportedRoleClassType)
        {
            // copy Element to the Document of this Class
            var ie = this.CopyInternalElementToDocument(caexObject);

            // Change the RoleRequirements
            ie.RoleRequirements[0].RefBaseRoleClassPath.Value = supportedRoleClassType.RefRoleClassPath.Value;

            var internalElementModel = new ImportInternalElementViewModel(ie)
                {
                   LibraryCaexObject = this.GetInstanceHierarchy(),
                   ContainerViewModel = this
                };
            internalElementModel.LoadCaexToRepresentationModel();
            this.ImportedInstancesCollection.Elements.Add(internalElementModel);
        }

        /// <summary>
        ///  Delete all InternalElements, which are not selected for a ClassToClass Mapping, but have duplicates in the
        ///  appedWithClassToClassMapping Collection.
        /// </summary>
        /// <param name="mappedWithClassToClassMapping">
        ///  The Classes with Mappings
        /// </param>
        internal void DeleteInstancesNotMappedToClass(
            List<ImportInternalElementViewModel> mappedWithClassToClassMapping)
        {
            var obsoleteElements =
                this.ImportedInstancesCollection.Elements.Where(
                    i =>
                    !i.MapToClass
                    && mappedWithClassToClassMapping.Any(c => c.CaexObject.ID.Value == i.CaexObject.ID.Value)).ToList();

            foreach (var obsolete in obsoleteElements)
            {
                obsolete.CaexObject.Remove();
                this.ImportedInstancesCollection.Elements.Remove(obsolete);
            }
        }

        /// <summary>
        ///  Copy an internal element from one document-context to another
        /// </summary>
        /// <param name="caexObject">
        ///  The original Element, which should be copied
        /// </param>
        /// <returns>
        ///  The CAEX_ClassModel.InternalElementType. 
        /// </returns>
        private InternalElementType CopyInternalElementToDocument(CAEXObject caexObject)
        {
            // copy Element to another Document
            var ownerDocument = this.CaexObject.Node.OwnerDocument;
            if (ownerDocument != null)
            {
                var node = ownerDocument.ImportNode(caexObject.clone().Node, true);
                var ie = node.CreateCAEXWrapper<InternalElementType>();

                // Change SystemClass Reference
                ie.RefBaseSystemUnitPath.Value = this.CaexObject.CAEXPath();

                // Keep the Objects ID
                ie.ID.Value = caexObject.ID.Value;

                // Add it to my InstanceHierarchy
                var ih = this.GetInstanceHierarchy();
                if (ih != null)
                {
                    ih.Insert_InternalElement(ie);
                }

                return ie;
            }

            return null;
        }

        /// <summary>
        ///   get all Instances of the SystemUnitClass in the current Caex-document
        /// </summary>
        /// <returns> The System.Xml.XmlNodeList. </returns>
        private XmlNodeList GetAllSystemUnitClassInstances()
        {
            var document = this.CaexObject.Node.OwnerDocument;
            var classPath = this.CaexObject.CAEXPath();

            var searchString = "//" + CAEX_CLASSModel_TagNames.INTERNALELEMENT_STRING + "[@RefBaseSystemUnitPath='"
                                    + classPath + "']";
            return document != null ? document.SelectNodes(searchString) : null;
        }

        /// <summary>
        ///   get the instance hierarchy of the system
        /// </summary>
        /// <returns> The CAEX_ClassModel.InstanceHierarchyType. </returns>
        private InstanceHierarchyType GetInstanceHierarchy()
        {
            if (this.TheSimulatedSystem != null && this.TheSimulatedSystem.AmlDocumentRepresentation != null)
            {
                if (this.TheSimulatedSystem.AmlDocumentRepresentation.CaexObject.InstanceHierarchy.Exists)
                {
                    foreach (InstanceHierarchyType ih in
                        this.TheSimulatedSystem.AmlDocumentRepresentation.CaexObject.InstanceHierarchy)
                    {
                        return ih;
                    }
                }

                return
                    this.TheSimulatedSystem.AmlDocumentRepresentation.CaexObject.New_InstanceHierarchy(
                        this.TheSimulatedSystem.Name);
            }

            return null;
        }

        /// <summary>
        ///  Event Handler, to validate the <see cref="ImportedInstancesCollection"/> raised after Changes from any Command 
        /// </summary>
        /// <param name="sender">
        ///  The sender. 
        /// </param>
        /// <param name="commandArgs">
        ///  The command args. 
        /// </param>
        private void ImportedInstancesCollectionAfterExecuteCommand(
            object sender, CollectionCommandEventArgs<ImportInternalElementViewModel> commandArgs)
        {
            if (commandArgs.Command == this.ImportedInstancesCollection.DeleteCommand)
            {
                this.ReValidate();
            }
        }

        /// <summary>
        ///  Event Handler, to validate the <see cref="InstancesCollection"/> raised after Changes from any Command 
        /// </summary>
        /// <param name="sender">
        ///  The sender. 
        /// </param>
        /// <param name="commandArgs">
        ///  The command args. 
        /// </param>
        private void InstancesCollectionAfterExecuteCommand(
            object sender, CollectionCommandEventArgs<InternalElementViewModel> commandArgs)
        {
            if (commandArgs.Command == this.InstancesCollection.DeleteCommand)
            {
                this.TheSimulatedSystem.ReValidate();
            }
        }

        /// <summary>
        ///  Test Method, to check if any command execution is allowed on the <see cref="InstancesCollection"/>.
        /// </summary>
        /// <param name="sender">
        ///  The sender. 
        /// </param>
        /// <param name="commandArgs">
        ///  The command args. 
        /// </param>
        private void InstancesCollectionAllowCommand(
            object sender, CollectionCommandEventArgs<InternalElementViewModel> commandArgs)
        {
            if (commandArgs.Command == this.InstancesCollection.AddCommand)
            {
                commandArgs.AllowCommand = this.RolesCollection.View.CurrentItem is RoleClassViewModel;
            }
        }

        /// <summary>
        /// Test Method, to check if any command execution is allowed on the <see cref="PropertySetCollection"/>.
        /// </summary>
        /// <param name="sender">
        /// The sender. 
        /// </param>
        /// <param name="commandArgs">
        /// The command args. 
        /// </param>
        private void PropertySetCollectionAllowCommand(
            object sender, CollectionCommandEventArgs<PropertySetViewModel> commandArgs)
        {
            if (commandArgs.Command == this.PropertySetCollection.AddCommand)
            {
                if (this.TheSimulatedSystem.RolesCollection.Elements.Count > 0
                    && this.TheSimulatedSystem.RolesCollection.View.CurrentItem is RoleClassViewModel)
                {
                    var element = this.TheSimulatedSystem.RolesCollection.View.CurrentItem as RoleClassViewModel;
                    commandArgs.AllowCommand = (element.CaexObject as RoleFamilyType).IsPropertySetRoleClass();
                }
                else
                {
                    commandArgs.AllowCommand = false;
                }
            }
        }

        /// <summary>
        /// Test Method, to check if any command execution is allowed on the <see cref="RolesCollection"/>.
        /// </summary>
        /// <param name="sender">
        /// The sender. 
        /// </param>
        /// <param name="commandArgs">
        /// The command args. 
        /// </param>
        private void RolesCollectionAllowCommand(
            object sender, CollectionCommandEventArgs<RoleClassViewModel> commandArgs)
        {
            if (commandArgs.Command == this.RolesCollection.AddCommand)
            {
                commandArgs.AllowCommand = this.TheSimulatedSystem.RolesCollection.Elements.Count > 0
                                           && this.TheSimulatedSystem.RolesCollection.View.CurrentItem != null
                                           &&
                                           !this.RolesCollection.Elements.Contains(
                                               this.TheSimulatedSystem.RolesCollection.View.CurrentItem);
            }
        }

        /// <summary>
        ///  Method, performed before a command action is executed on the <see cref="RolesCollection"/>.
        /// </summary>
        /// <param name="sender">
        ///  The sender. 
        /// </param>
        /// <param name="commandArgs">
        ///  The command args. 
        /// </param>
        private void RolesCollectionBeforeExecuteCommand(
            object sender, CollectionCommandEventArgs<RoleClassViewModel> commandArgs)
        {
            if (commandArgs.Command == this.RolesCollection.DeleteCommand)
            {
                var roleClass = commandArgs.ActiveItem;
                SupportedRoleClassType deleteThisSupportedRole = null;

                foreach (
                    var src in
                        this.CaexObject.SupportedRoleClass.Cast<SupportedRoleClassType>().Where(
                            src =>
                            src.RefRoleClassPath.Exists()
                            && src.RefRoleClassPath.Value == roleClass.CaexObject.CAEXPath()))
                {
                    deleteThisSupportedRole = src;
                }

                if (deleteThisSupportedRole != null)
                {
                    deleteThisSupportedRole.Remove();
                }

                this.RolesCollection.Elements.Remove(roleClass);
                commandArgs.CancelCommand = true;

                OnNotifyPropertyChanged("HasSupportedRoles");
            }
        }

        #endregion
    }
}